﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using STP;

namespace Simulator
{
	public enum Side
	{
		Bottom,
		Right,
		Top,
		Left,
	}

	/// <summary>
	/// This class is only used for drawing and for the PropertyGrid.
	/// Do not implement any Ethernet logic here.
	/// </summary>
	public class Port
	{
		#region statics init
		static readonly Bitmap[] bitmapsDesignatedDiscarding		= new Bitmap [4];
		static readonly Bitmap[] bitmapsDesignatedLearning			= new Bitmap [4];
		static readonly Bitmap[] bitmapsDesignatedForwarding		= new Bitmap [4];
		static readonly Bitmap[] bitmapsDesignatedForwardingOperEdge= new Bitmap [4];

		static readonly Bitmap[] bitmapsRootOrMasterDiscarding		= new Bitmap [4];
		static readonly Bitmap[] bitmapsRootOrMasterLearning		= new Bitmap [4];
		static readonly Bitmap[] bitmapsRootOrMasterForwarding		= new Bitmap [4];

		static readonly Bitmap[] bitmapsAlternateDiscarding			= new Bitmap [4];
		static readonly Bitmap[] bitmapsAlternateLearning			= new Bitmap [4];
		static readonly Bitmap[] bitmapsBackupDiscarding			= new Bitmap [4];
		static readonly Bitmap[] bitmapsDisabled					= new Bitmap [4];

		static readonly Bitmap[] bitmapsUndefined					= new Bitmap [4];

		static void SetBitmaps (Bitmap[] bitmaps, Bitmap bitmap)
		{
			bitmaps [0] = new Bitmap (bitmap);
			bitmaps [0].RotateFlip (RotateFlipType.RotateNoneFlipNone);

			bitmaps [1] = new Bitmap (bitmap);
			bitmaps [1].RotateFlip (RotateFlipType.Rotate90FlipNone);

			bitmaps [2] = new Bitmap (bitmap);
			bitmaps [2].RotateFlip (RotateFlipType.Rotate180FlipNone);

			bitmaps [3] = new Bitmap (bitmap);
			bitmaps [3].RotateFlip (RotateFlipType.Rotate270FlipNone);
		}

		static Port ()
		{
			Pen penDiscardingPort = new Pen (Color.Red, 1);
			Brush brushDiscardingPort = Brushes.Red;

			Pen penLearningPort = new Pen (Color.Yellow, 1);
			Brush brushLearningPort = Brushes.Yellow;

			Pen penForwardingPort = new Pen (Color.Green, 1);
			Brush brushForwardingPort = Brushes.Green;

			Debug.Assert ((ExteriorDrawingWidth % 2) == 0);
			Debug.Assert ((ExteriorDrawingHeight % 2) == 0);

			float circleDiameter = Math.Min (ExteriorDrawingHeight / 2, ExteriorDrawingWidth) - 2;

			float discardingFirstHorizontalLineY = circleDiameter + (ExteriorDrawingHeight - circleDiameter) / 3;
			float discardingSecondHorizontalLineY = circleDiameter + (ExteriorDrawingHeight - circleDiameter) * 2 / 3;
			float learningHorizontalLineY = circleDiameter + (ExteriorDrawingHeight - circleDiameter) / 2;

			Bitmap bitmap = new Bitmap ((int) ExteriorDrawingWidth, (int) ExteriorDrawingHeight, PixelFormat.Format32bppArgb);

			using (Graphics g = Graphics.FromImage (bitmap))
			{
				// ------------------------------------------------------------------------
				// disabled
				g.Clear (Color.Transparent);
				g.DrawLine (penDiscardingPort, ExteriorDrawingWidth / 2, 0, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.DrawLine (penDiscardingPort, 0, ExteriorDrawingHeight / 3, ExteriorDrawingWidth, ExteriorDrawingHeight * 2 / 3);
				SetBitmaps (bitmapsDisabled, bitmap);

				// ------------------------------------------------------------------------
				// designated discarding
				g.Clear (Color.Transparent);
				g.DrawLine (penDiscardingPort, ExteriorDrawingWidth / 2, circleDiameter, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.FillEllipse (brushDiscardingPort, ExteriorDrawingWidth / 2 - circleDiameter / 2, 0, circleDiameter, circleDiameter);
				g.DrawLine (penDiscardingPort, 0, discardingFirstHorizontalLineY, ExteriorDrawingWidth, discardingFirstHorizontalLineY);
				g.DrawLine (penDiscardingPort, 0, discardingSecondHorizontalLineY, ExteriorDrawingWidth, discardingSecondHorizontalLineY);
				SetBitmaps (bitmapsDesignatedDiscarding, bitmap);

				// designated learning
				g.Clear (Color.Transparent);
				g.DrawLine (penLearningPort, ExteriorDrawingWidth / 2, circleDiameter, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.FillEllipse (brushLearningPort, ExteriorDrawingWidth / 2 - circleDiameter / 2, 0, circleDiameter, circleDiameter);
				g.DrawLine (penLearningPort, 0, learningHorizontalLineY, ExteriorDrawingWidth, learningHorizontalLineY);
				SetBitmaps (bitmapsDesignatedLearning, bitmap);

				// designated forwarding
				g.Clear (Color.Transparent);
				g.DrawLine (penForwardingPort, ExteriorDrawingWidth / 2, circleDiameter, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.FillEllipse (brushForwardingPort, ExteriorDrawingWidth / 2 - circleDiameter / 2, 0, circleDiameter, circleDiameter);
				SetBitmaps (bitmapsDesignatedForwarding, bitmap);

				// designated forwarding operEdge
				g.Clear (Color.Transparent);
				g.FillEllipse (brushForwardingPort, ExteriorDrawingWidth / 2 - circleDiameter / 2, 0, circleDiameter, circleDiameter);
				g.DrawLine (penForwardingPort, ExteriorDrawingWidth / 2, circleDiameter, 1, circleDiameter + (ExteriorDrawingHeight - circleDiameter) / 2);
				g.DrawLine (penForwardingPort, ExteriorDrawingWidth / 2, circleDiameter, ExteriorDrawingWidth - 1, circleDiameter + (ExteriorDrawingHeight - circleDiameter) / 2);
				g.DrawLine (penForwardingPort, ExteriorDrawingWidth / 2, ExteriorDrawingHeight, 1, circleDiameter + (ExteriorDrawingHeight - circleDiameter) / 2);
				g.DrawLine (penForwardingPort, ExteriorDrawingWidth / 2, ExteriorDrawingHeight, ExteriorDrawingWidth - 1, circleDiameter + (ExteriorDrawingHeight - circleDiameter) / 2);
				SetBitmaps (bitmapsDesignatedForwardingOperEdge, bitmap);

				// ------------------------------------------------------------------------
				// root or master discarding
				g.Clear (Color.Transparent);
				g.DrawLine (penDiscardingPort, ExteriorDrawingWidth / 2, circleDiameter, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.DrawEllipse (penDiscardingPort, ExteriorDrawingWidth / 2 - circleDiameter / 2, 0, circleDiameter, circleDiameter);
				g.DrawLine (penDiscardingPort, 0, discardingFirstHorizontalLineY, ExteriorDrawingWidth, discardingFirstHorizontalLineY);
				g.DrawLine (penDiscardingPort, 0, discardingSecondHorizontalLineY, ExteriorDrawingWidth, discardingSecondHorizontalLineY);
				SetBitmaps (bitmapsRootOrMasterDiscarding, bitmap);

				// root or master learning
				g.Clear (Color.Transparent);
				g.DrawLine (penLearningPort, ExteriorDrawingWidth / 2, circleDiameter, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.DrawEllipse (penLearningPort, ExteriorDrawingWidth / 2 - circleDiameter / 2, 0, circleDiameter, circleDiameter);
				g.DrawLine (penLearningPort, 0, learningHorizontalLineY, ExteriorDrawingWidth, learningHorizontalLineY);
				SetBitmaps (bitmapsRootOrMasterLearning, bitmap);

				// root or master forwarding
				g.Clear (Color.Transparent);
				g.DrawLine (penForwardingPort, ExteriorDrawingWidth / 2, circleDiameter, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.DrawEllipse (penForwardingPort, ExteriorDrawingWidth / 2 - circleDiameter / 2, 0, circleDiameter, circleDiameter);
				SetBitmaps (bitmapsRootOrMasterForwarding, bitmap);

				// ------------------------------------------------------------------------
				// Alternate discarding
				g.Clear (Color.Transparent);
				g.DrawLine (penDiscardingPort, ExteriorDrawingWidth / 2, 0, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.DrawLine (penDiscardingPort, 0, discardingFirstHorizontalLineY, ExteriorDrawingWidth, discardingFirstHorizontalLineY);
				g.DrawLine (penDiscardingPort, 0, discardingSecondHorizontalLineY, ExteriorDrawingWidth, discardingSecondHorizontalLineY);
				SetBitmaps (bitmapsAlternateDiscarding, bitmap);

				// Alternate learning
				g.Clear (Color.Transparent);
				g.DrawLine (penLearningPort, ExteriorDrawingWidth / 2, 0, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.DrawLine (penLearningPort, 0, learningHorizontalLineY, ExteriorDrawingWidth, learningHorizontalLineY);
				SetBitmaps (bitmapsAlternateLearning, bitmap);

				// ------------------------------------------------------------------------
				// Backup discarding
				g.Clear (Color.Transparent);
				g.DrawLine (penDiscardingPort, ExteriorDrawingWidth / 2, 0, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.DrawLine (penDiscardingPort, 0, discardingFirstHorizontalLineY / 2, ExteriorDrawingWidth, discardingFirstHorizontalLineY / 2);
				g.DrawLine (penDiscardingPort, 0, discardingFirstHorizontalLineY, ExteriorDrawingWidth, discardingFirstHorizontalLineY);
				g.DrawLine (penDiscardingPort, 0, discardingSecondHorizontalLineY, ExteriorDrawingWidth, discardingSecondHorizontalLineY);
				SetBitmaps (bitmapsBackupDiscarding, bitmap);

				// ------------------------------------------------------------------------
				// undefined
				g.Clear (Color.Transparent);
				g.DrawLine (penDiscardingPort, ExteriorDrawingWidth / 2, 0, ExteriorDrawingWidth / 2, ExteriorDrawingHeight);
				g.DrawString ("?", SystemFonts.DialogFont, brushDiscardingPort, 0, 0);
				SetBitmaps (bitmapsUndefined, bitmap);
			}
		}
		#endregion

		// these must be multiples of 2, cause they're halved here and there
		public const int InteriorDrawingLongSize = 30;
		public const int InteriorDrawingShortSize = 18;
		public const int ExteriorDrawingWidth = 16;
		public const int ExteriorDrawingHeight = 24;

		public const int InitialPortToPortSpacing = 20;
		public const int InitialEdgeToPortSpacing = 30;
		public const int SnappingRectangleSize = 20;

		public readonly Bridge Bridge;
		public readonly uint PortIndex;
		
		readonly PortTree [] _trees;
		readonly ReadOnlyCollection<PortTree> _treesReadOnly;

		Side _side;
		int _offset;

		public Port (Bridge bridge, uint portIndex, uint treeCount, Side side, int offset)
		{
			this.Bridge = bridge;
			this.PortIndex = portIndex;
			_side = side;
			_offset = offset;

			_trees = new PortTree [treeCount];
			for (uint treeIndex = 0; treeIndex < treeCount; treeIndex++)
				_trees [treeIndex] = new PortTree (this, treeIndex);

			_treesReadOnly = new ReadOnlyCollection<PortTree> (_trees);
		}

		[Browsable (false)]
		public Side Side
		{
			get { return _side; }
			set { _side = value; }
		}

		[Browsable (false)]
		public int Offset
		{
			get { return _offset; }
			set { _offset = value; }
		}

		[Browsable (false)]
		public ReadOnlyCollection<PortTree> Trees
		{
			get { return _treesReadOnly; }
		}

		public static Bitmap GetBitmap (PortRole role, bool learning, bool forwarding, bool operEdge, RotateFlipType rotateFlip)
		{
			int bitmapIndex;
			if (rotateFlip == RotateFlipType.RotateNoneFlipNone)
				bitmapIndex = 0;
			else if (rotateFlip == RotateFlipType.Rotate90FlipNone)
				bitmapIndex = 1;
			else if (rotateFlip == RotateFlipType.Rotate180FlipNone)
				bitmapIndex = 2;
			else if (rotateFlip == RotateFlipType.Rotate270FlipNone)
				bitmapIndex = 3;
			else
				throw new Exception ();

			if (role == PortRole.Disabled)
				return bitmapsDisabled [bitmapIndex];

			else if ((role == PortRole.Designated) && !learning && !forwarding)
				return bitmapsDesignatedDiscarding [bitmapIndex];
			else if ((role == PortRole.Designated) && learning && !forwarding)
				return bitmapsDesignatedLearning [bitmapIndex];
			else if ((role == PortRole.Designated) && learning && forwarding && !operEdge)
				return bitmapsDesignatedForwarding [bitmapIndex];
			else if ((role == PortRole.Designated) && learning && forwarding && operEdge)
				return bitmapsDesignatedForwardingOperEdge [bitmapIndex];

			else if (((role == PortRole.Root) || (role == PortRole.Master)) && !learning && !forwarding)
				return bitmapsRootOrMasterDiscarding [bitmapIndex];
			else if (((role == PortRole.Root) || (role == PortRole.Master)) && learning && !forwarding)
				return bitmapsRootOrMasterLearning [bitmapIndex];
			else if (((role == PortRole.Root) || (role == PortRole.Master)) && learning && forwarding)
				return bitmapsRootOrMasterForwarding [bitmapIndex];

			else if ((role == PortRole.Alternate) && !learning && !forwarding)
				return bitmapsAlternateDiscarding [bitmapIndex];
			else if ((role == PortRole.Alternate) && learning && !forwarding)
				return bitmapsAlternateLearning [bitmapIndex];

			else if ((role == PortRole.Backup) && !learning && !forwarding)
				return bitmapsBackupDiscarding [bitmapIndex];

			else if (role == PortRole.Undefined)
				return bitmapsUndefined [bitmapIndex];

			else
				throw new NotImplementedException ();
		}

		public Bitmap GetBitmap (int treeIndex, RotateFlipType rotateFlip)
		{
			if (treeIndex == -1)
				return GetBitmap (PortRole.Disabled, false, false, true, rotateFlip);

			PortRole role = _trees [treeIndex].role;
			bool learning = _trees [treeIndex].learning;
			bool forwarding = _trees [treeIndex].forwarding;

			return GetBitmap (role, learning, forwarding, this.operEdge, rotateFlip);
		}

		public RectangleF GetInteriorRect ()
		{
			RectangleF r = new RectangleF ();

			if (_side == Side.Bottom)
			{
				r.X = Bridge.X + _offset - Port.InteriorDrawingLongSize / 2;
				r.Y = Bridge.Y + Bridge.Height - Port.InteriorDrawingShortSize;
				r.Width = Port.InteriorDrawingLongSize;
				r.Height = Port.InteriorDrawingShortSize;
			}
			else if (_side == Side.Left)
			{
				r.X = Bridge.X;
				r.Y = Bridge.Y + _offset - Port.InteriorDrawingLongSize / 2;
				r.Width = Port.InteriorDrawingShortSize;
				r.Height = Port.InteriorDrawingLongSize;
			}
			else if (_side == Side.Top)
			{
				r.X = Bridge.X + _offset - Port.InteriorDrawingLongSize / 2;
				r.Y = Bridge.Y;
				r.Width = Port.InteriorDrawingLongSize;
				r.Height = Port.InteriorDrawingShortSize;
			}
			else // if (_side == Side.Right)
			{
				r.X = Bridge.X + Bridge.Width - Port.InteriorDrawingShortSize;
				r.Y = Bridge.Y + _offset - Port.InteriorDrawingLongSize / 2;
				r.Width = Port.InteriorDrawingShortSize;
				r.Height = Port.InteriorDrawingLongSize;
			}

			return r;
		}

		[Browsable (false)]
		public Point ConnectorLocation
		{
			get
			{
				if (_side == Side.Bottom)
				{
					return new Point (Bridge.X + _offset, Bridge.Y + Bridge.Height + Port.ExteriorDrawingHeight);
				}
				else if (_side == Side.Left)
				{
					return new Point (Bridge.X - Port.ExteriorDrawingHeight + 1, Bridge.Y + _offset);
				}
				else if (_side == Side.Top)
				{
					return new Point (Bridge.X + _offset, Bridge.Y - Port.ExteriorDrawingHeight + 1);
				}
				else // if (_side == Side.Right)
				{
					return new Point (Bridge.X + Bridge.Width + Port.ExteriorDrawingHeight, Bridge.Y + _offset);
				}
			}
		}

		[Description ("This is the operEdge parameter described in §13.25.30 in 802.1Q-2011.")]
		public bool operEdge 
		{
			get { return Bridge.Wrapper.GetPortOperEdge (PortIndex); }
		}

		[Description ("This is the operPointToPointMAC parameter described in §6.6.3 in 802.1Q-2011. The Simulator always sets it.")]
		public bool operPointToPointMAC
		{
			get { return Bridge.Wrapper.GetPortOperPointToPointMAC (PortIndex); }
		}

		[Description ("This is the portEnabled parameter described in §13.25.31 in 802.1Q-2011.")]
		public bool portEnabled
		{
			get { return Bridge.Wrapper.GetPortEnabled (PortIndex); }
		}

		[Description ("This is the AdminEdge parameter described in §13.25.1 in 802.1Q-2011.")]
		public bool AdminEdge
		{
			get { return Bridge.Wrapper.GetPortAdminEdge (PortIndex); }

			set 
			{
				lock (Bridge.WrapperLock)
					Bridge.Wrapper.SetPortAdminEdge (PortIndex, value); 
			}
		}

		// This property is set by the thread in SheetArea.cs whenever it detects a change in wiring of ports.
		// The code in Bridge.BridgeThreadProc reads it periodically and calls STP_OnPortEnabled/Disabled when it changes.
		// The behavior in Bridge.BridgeThreadProc is consistent with that from a real bridge, which also polls the port for its "operational" state.
		bool _macOperational;

		[Description ("This is the MAC_Operational parameter described in §6.6.7 in 802.1Q-2011. "
			+ "The Simulator sets it when the bridge is powered and a cable exists between this port and another port on a powered bridge, and clears it otherwise.")]
		public bool MAC_Operational 
		{
			get { return _macOperational; }

			internal set
			{
				if (_macOperational != value)
				{
					_macOperational = value;
					this.Bridge.SafeInvalidate ();
				}
			}
		}

		public override string ToString ()
		{
			return String.Format ("Port index {0} on {1}", PortIndex, Bridge);
		}
	}
}
